﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Windows;
using RootfusHostView;

namespace RootfusCoreComponents.IO
{
    /// <summary>
    /// Handles I/O to a file given a list of surface items.
    /// </summary>
    public class ItemFileOperator
    {
        private string _file_path;

        /// <summary>
        /// Remember the file we will be writing to.
        /// </summary>
        /// <param name="filename"></param>
        public ItemFileOperator(string filename)
        {
            _file_path = filename;
        }

        private RootfusFileData _context = new RootfusFileData();

        /// <summary>
        /// Remember a particular item for saving, along with its relavent GUI info!
        /// </summary>
        /// <param name="rootfusSurfaceItemWrapper"></param>
        /// <param name="sIUIInfo"></param>
        public void AddSurfaceItem(RootfusSurfaceItemWrapper item, SIUIInfo guiInfo)
        {
            ///
            /// Basic info about the item.
            /// 

            SItem tofile = new SItem();
            tofile._name = item.UserName;
            tofile._gui_info = guiInfo;
            tofile._addin_name = item.AddInName;
            tofile._addin_config = item.ItemConfigXML;

            ///
            /// First, record all the connections that are landing on this
            /// guy.
            ///

            foreach (var c in item.InputPinConfiguration)
            {
                List<SIInputConnection.SIInputLanding> actual_connections = new List<SIInputConnection.SIInputLanding>();
                switch (c.ConnectionType)
                {
                    case PinConnectionType.PinInfinite:
                        PinConnectionInfinite ic = c as PinConnectionInfinite;
                        foreach (var input in ic.PinSources)
                        {
                            if (input.Item != null && input.Pin != null)
                            {
                                actual_connections.Add(new SIInputConnection.SIInputLanding(input.Item.UserName, input.Pin));
                            }
                        }
                        break;
                    case PinConnectionType.PinSingle:
                        PinConnectionSingle sc = c as PinConnectionSingle;
                        if (sc.PinSource != null && sc.PinSource.Item != null && sc.PinSource.Pin != null)
                        {
                            actual_connections.Add(new SIInputConnection.SIInputLanding(sc.PinSource.Item.UserName, sc.PinSource.Pin));
                        }
                        break;
                    default:
                        break;
                }
                tofile._configured_pins.Add(new SIInputConnection(c.Name, actual_connections));
            }

            ///
            /// Keep it around to save it! :-)
            /// 

            _context._surface_items.Add(tofile);
        }

        /// <summary>
        /// Track the gui lines between the various surface items.
        /// </summary>
        /// <param name="rootfusSurfaceItemWrapper"></param>
        /// <param name="rootfusSurfaceItemWrapper_2"></param>
        public void AddSIGUIConnection(RootfusSurfaceItemWrapper source_item, RootfusSurfaceItemWrapper sink_item)
        {
            _context._gui_connections.Add(new SIGUIConnection(source_item.UserName, sink_item.UserName));
        }

        /// <summary>
        /// Actually write this puppy out to a file!
        /// </summary>
        public void Save()
        {
            using (StreamWriter wr = File.CreateText(_file_path))
            {
                RFDSer.Serialize(wr, _context);
                wr.Close();
            }
        }

        /// <summary>
        /// Callback to create the surface item and the appropriate place.
        /// </summary>
        /// <param name="addinname"></param>
        /// <param name="username"></param>
        /// <param name="top"></param>
        /// <param name="left"></param>
        public delegate RootfusSurfaceItemWrapper CreateAddin(string addinname, string username, Point position, Size size);

        /// <summary>
        /// Call back to connect GUI connection between two items.
        /// </summary>
        /// <param name="source_item_name"></param>
        /// <param name="sink_item_name"></param>
        public delegate void CreateGUIConnection(string source_item_name, string sink_item_name);

        private static XmlSerializer _ser = null;

        /// <summary>
        /// A "local" singlton. Not thread safe, but shouldn't matter.
        /// </summary>
        private XmlSerializer RFDSer
        {
            get
            {
                if (_ser == null)
                {
                    _ser = new XmlSerializer(typeof(RootfusFileData));
                }
                return _ser;
            }
        }

        /// <summary>
        /// Load a file guy from a file.
        /// </summary>
        public void Load(CreateAddin createCallback, CreateGUIConnection guiCallback)
        {
            using (StreamReader rd = File.OpenText(_file_path))
            {
                RootfusFileData data = (RootfusFileData)RFDSer.Deserialize(rd);

                ///
                /// First job -- create the add-ins. 
                /// 

                Dictionary<string, RootfusSurfaceItemWrapper> wrappers = new Dictionary<string, RootfusSurfaceItemWrapper>();
                foreach (var item in data._surface_items)
                {
                    wrappers[item._name] = createCallback(item._addin_name, item._name,
                        new Point(item._gui_info._position_left, item._gui_info._position_top),
                        new Size(item._gui_info._size_x, item._gui_info._size_y));
                    wrappers[item._name].Updated = false;
                }

                ///
                /// And the GUI connections between them.
                /// 

                foreach (var c in data._gui_connections)
                {
                    guiCallback(c._output_item_name, c._input_item_name);
                }

                ///
                /// Load up the guys with their own config data. We do this on purpose before we make the connections so we can
                /// setup things in an orderly fasion!
                /// 

                foreach (var item in data._surface_items)
                {
                    if (item._addin_config != null)
                    {
                        wrappers[item._name].ItemConfigXML = item._addin_config;
                    }
                }

                ///
                /// Create the actual pin connections. Now, unfortunately for us, some of these guys
                /// have dynamic output pins.
                /// 

                Dictionary<string, bool> item_configured = new Dictionary<string,bool>();
                int configured_last_pass = 1;
                while (configured_last_pass > 0)
                {
                    configured_last_pass = 0;
                    foreach (var item in data._surface_items)
                    {
                        if (!item_configured.ContainsKey(item._name))
                        {
                            item_configured[item._name] = false;
                        }
                        if (!item_configured[item._name])
                        {
                            bool conifgured_all_input_pins = true;
                            foreach (var c in item._configured_pins)
                            {
                                /// Find the sink
                                RootfusSurfaceItemWrapper sink_wrapper = wrappers[item._name];

                                /// Find all source(s).
                                List<IncomingPinConnectionInfo> landings = new List<IncomingPinConnectionInfo>();
                                foreach (var l in c._input_landings)
                                {
                                    RootfusSurfaceItemWrapper source_wrapper = wrappers[l._output_item_name];
                                    string source_pin = l._output_pin_name;
                                    if (source_wrapper == null || source_pin == null)
                                    {
                                        conifgured_all_input_pins = false;
                                        break;
                                    }
                                    landings.Add(new IncomingPinConnectionInfo(source_wrapper, source_pin));
                                }

                                /// Make sure we aren't lacking in pin connections here!
                                if (sink_wrapper == null || landings.Count == 0)
                                {
                                    conifgured_all_input_pins = false;
                                    break;
                                }

                                /// Now, connect them to the sink.
                                sink_wrapper.SetPinConfig(c._input_pin_name, landings);
                            }
                            if (conifgured_all_input_pins)
                            {
                                item_configured[item._name] = true;
                                configured_last_pass += 1;
                                wrappers[item._name].Update();
                            }
                        }                        
                    }
                }

                ///
                /// Most of the update commands have now been issued. I admit that it isn't the most efficency way to
                /// do it, but when you can have dynamic output pin names, you have to do something like this.
                /// Lets check, however, and make sure that everything has really been done.
                /// 

                foreach (var item in from x in wrappers.Values where x.Updated == false select x)
                {
                    item.Update();
                }

            }
        }
    }
}
